Skip to content

Fix chinese input and text pasting#1479

Merged
deepin-bot[bot] merged 2 commits intolinuxdeepin:masterfrom
yixinshark:fix-chineseInput
Mar 5, 2026
Merged

Fix chinese input and text pasting#1479
deepin-bot[bot] merged 2 commits intolinuxdeepin:masterfrom
yixinshark:fix-chineseInput

Conversation

@yixinshark
Copy link
Contributor

@yixinshark yixinshark commented Mar 5, 2026

Summary by Sourcery

Improve Wayland dock plugin text input handling and clipboard integration to fix IME typing and paste behavior in plugins.

New Features:

  • Add a text input focus proxy that wires plugin text input protocols to the compositor so plugin surfaces can receive IME events correctly.
  • Synchronize the host system clipboard into the Wayland compositor selection for plugins when the clipboard changes, enabling paste operations from the host into plugins.

Bug Fixes:

  • Fix Chinese and other IME-based text input not committing into plugin input fields by ensuring QWaylandQuickItem surfaces gain active focus when IME is enabled and text input protocols have correct focus.

Enhancements:

  • Register only the Qt text input method manager on the compositor to align server and client protocol expectations and avoid conflicts with v2/v3 text input protocols.
  • Improve handling of QtWayland text input private APIs to correctly manage focus transitions and input panel visibility for plugin surfaces.

@sourcery-ai
Copy link

sourcery-ai bot commented Mar 5, 2026

Reviewer's Guide

Adds proper Wayland text input/IME focus handling and clipboard synchronization so plugin Wayland clients can use Chinese input and paste text correctly, primarily by wiring Qt’s private text input protocols to seat focus changes and ensuring the correct QQuickItem gains active focus.

Sequence diagram for IME focus and Chinese input forwarding

sequenceDiagram
    actor User
    participant PluginClient
    participant PluginSurface
    participant PluginTextInput
    participant QWaylandSeat
    participant PluginManager
    participant TextInputExtensions
    participant QWaylandQuickItem
    participant QInputMethod

    User->>PluginClient: Clicks input field
    PluginClient->>PluginSurface: Give keyboard focus
    PluginClient->>PluginTextInput: text_input.enable

    PluginSurface->>QWaylandSeat: Keyboard focus change
    QWaylandSeat-->>PluginManager: keyboardFocusChanged(newFocus)

    PluginManager->>PluginSurface: newFocus.updateSelection()
    PluginManager->>TextInputExtensions: Iterate seat.extensions()
    Note over TextInputExtensions,PluginManager: For each extension
    TextInputExtensions-->>PluginManager: QWaylandTextInput
    PluginManager->>TextInputExtensions: QWaylandTextInputPrivate.setFocus(newFocus)
    PluginManager->>TextInputExtensions: connect(surfaceEnabled, onTextInputSurfaceEnabled)

    TextInputExtensions-->>PluginManager: QWaylandTextInputV3
    PluginManager->>TextInputExtensions: QWaylandTextInputV3Private.setFocus(newFocus)
    PluginManager->>TextInputExtensions: connect(surfaceEnabled, onTextInputSurfaceEnabled)

    TextInputExtensions-->>PluginManager: QWaylandQtTextInputMethod
    PluginManager->>TextInputExtensions: QWaylandQtTextInputMethodPrivate.inputPanelVisible = true
    PluginManager->>TextInputExtensions: WlQtTextInputMethodHelper.setFocusCustom(newFocus)
    PluginManager->>TextInputExtensions: connect(surfaceEnabled, onTextInputSurfaceEnabled)

    PluginTextInput-->>TextInputExtensions: surfaceEnabled(PluginSurface)
    TextInputExtensions-->>PluginManager: surfaceEnabled(PluginSurface)

    PluginManager->>PluginManager: onTextInputSurfaceEnabled(surface)
    PluginManager->>PluginSurface: surface.views()
    PluginSurface-->>PluginManager: view with QWaylandQuickItem
    PluginManager->>QWaylandQuickItem: forceActiveFocus

    User->>QInputMethod: Type Chinese text
    QInputMethod-->>QWaylandQuickItem: QInputMethodEvent
    QWaylandQuickItem-->>TextInputExtensions: QWaylandInputMethodControl.inputMethodEvent
    TextInputExtensions-->>PluginTextInput: commit_string
    PluginTextInput-->>PluginClient: Insert committed Chinese text
Loading

Sequence diagram for clipboard synchronization and paste

sequenceDiagram
    actor User
    participant HostApp
    participant HostClipboard
    participant PluginManager
    participant QWaylandCompositor
    participant PluginClient

    User->>HostApp: Copy text
    HostApp->>HostClipboard: Set clipboard data
    HostClipboard-->>PluginManager: changed(mode)

    PluginManager->>HostClipboard: mimeData(mode)
    HostClipboard-->>PluginManager: QMimeData
    PluginManager->>QWaylandCompositor: setRetainedSelectionEnabled(true) (init)
    PluginManager->>QWaylandCompositor: overrideSelection(mimeData)

    QWaylandCompositor-->>PluginClient: Update Wayland selection

    User->>PluginClient: Paste
    PluginClient->>QWaylandCompositor: Request selection data
    QWaylandCompositor-->>PluginClient: Send clipboard contents
Loading

Class diagram for updated PluginManager text input handling

classDiagram
    class PluginManager {
        +PluginManager(QWaylandCompositor* compositor)
        +void initialize()
        +void setupMouseFocusListener()
        +void setupTextInputProxy(QWaylandCompositor* compositor)
        +void onTextInputSurfaceEnabled(QWaylandSurface* surface)
    }

    class WlQtTextInputMethodHelper {
        +static void setFocusCustom(QWaylandQtTextInputMethodPrivate* d, QWaylandSurface* surface)
    }

    class QWaylandCompositor {
        +QWaylandSeat* defaultSeat()
        +void setRetainedSelectionEnabled(bool enabled)
        +void overrideSelection(const QMimeData* mimeData)
    }

    class QWaylandSeat {
        +QList~QObject*~ extensions()
        +Q_SIGNAL void keyboardFocusChanged(QWaylandSurface* newFocus, QWaylandSurface* oldFocus)
    }

    class QWaylandSurface {
        +QList~QWaylandView*~ views()
        +void updateSelection()
        +QWaylandClient* client()
        +QtWaylandServer::wl_surface* resource()
    }

    class QWaylandView {
        +QQuickItem* renderObject()
    }

    class QWaylandQuickItem {
        +void forceActiveFocus(Qt::FocusReason reason)
    }

    class QWaylandTextInputPrivate {
        +void setFocus(QWaylandSurface* surface)
        QWaylandSurface* focusedSurface
    }

    class QWaylandTextInputV3Private {
        +void setFocus(QWaylandSurface* surface)
        QWaylandSurface* focusedSurface
    }

    class QWaylandQtTextInputMethodPrivate {
        QWaylandSurface* focusedSurface
        QtWaylandServer::qt_text_input_method_v1::Resource* resource
        bool inputPanelVisible
        QWaylandDestroyListener focusDestroyListener
        QString surroundingText
        int cursorPosition
        int anchorPosition
        int absolutePosition
        QRect cursorRectangle
        QString preferredLanguage
        Qt::InputMethodHints hints
    }

    class qt_text_input_method_v1 {
        +QMap~wl_client*, Resource*~ resourceMap()
        +void send_enter(wl_resource* resource, wl_resource* surface)
        +void send_leave(wl_resource* resource, wl_resource* surface)
        +void send_input_direction_changed(wl_resource* resource, int direction)
        +void send_locale_changed(wl_resource* resource, QString locale)
    }

    PluginManager --> QWaylandCompositor : uses
    PluginManager --> QWaylandSeat : uses
    PluginManager --> QWaylandSurface : uses
    PluginManager --> QWaylandQuickItem : uses
    PluginManager --> QWaylandTextInputPrivate : uses
    PluginManager --> QWaylandTextInputV3Private : uses
    PluginManager --> QWaylandQtTextInputMethodPrivate : uses
    WlQtTextInputMethodHelper --|> qt_text_input_method_v1
    WlQtTextInputMethodHelper --> QWaylandQtTextInputMethodPrivate : manipulates
Loading

File-Level Changes

Change Details Files
Register and configure the Qt-specific text input manager so IME events use the qt_text_input_method protocol consistently.
  • Include QtWayland text input manager and private header types needed to manipulate text input focus and state.
  • Instantiate and initialize QWaylandQtTextInputMethodManager in PluginManager::initialize, intentionally avoiding registration of v2/v3 managers to prevent protocol preference mismatches between server and client.
  • Introduce WlQtTextInputMethodHelper to override/set focus on QWaylandQtTextInputMethodPrivate and emit proper enter, direction, and locale events while resetting internal text input state.
panels/dock/pluginmanagerextension.cpp
Proxy text input focus and IME enablement from plugin surfaces to the appropriate Wayland/Qt text input objects and QQuickItems, ensuring commit_string reaches plugin text fields (fixing Chinese input).
  • Add setupTextInputProxy to connect QWaylandSeat::keyboardFocusChanged, updating selection and propagating focus to QWaylandTextInput, QWaylandTextInputV3, and QWaylandQtTextInputMethod via their private implementations.
  • Use QObject meta-information and QObjectPrivate::get to access private text input objects and call their setFocus/new focus APIs despite missing exported symbols in the Deepin Qt6 build.
  • Connect each text input extension’s surfaceEnabled signal to a new onTextInputSurfaceEnabled slot with Qt::UniqueConnection, so that when a plugin enables IME, the corresponding QWaylandQuickItem is given active focus via forceActiveFocus.
  • Ensure QWaylandQtTextInputMethodPrivate::inputPanelVisible is set true and its focus is updated using the helper, allowing IME panels to show correctly.
panels/dock/pluginmanagerextension.cpp
panels/dock/pluginmanagerextension_p.h
Synchronize host clipboard with plugin Wayland clients to enable paste operations.
  • Enable retained selection on the compositor and hook QClipboard::changed to call QWaylandCompositor::overrideSelection with the current QMimeData for Clipboard and Selection modes.
  • Perform an initial synchronization of the host clipboard into the compositor’s selection on startup so paste works immediately.
panels/dock/pluginmanagerextension.cpp
Wire new text input proxying behavior into PluginManager via new API and slot.
  • Declare setupTextInputProxy and onTextInputSurfaceEnabled in PluginManager’s header and implement them in the source file.
  • Invoke setupTextInputProxy from PluginManager::initialize after mouse focus listener setup so IME handling is configured alongside other seat-related behavior.
panels/dock/pluginmanagerextension.cpp
panels/dock/pluginmanagerextension_p.h

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • The WlQtTextInputMethodHelper::setFocusCustom implementation does auto *base = static_cast<QtWaylandServer::qt_text_input_method_v1*>(d); and later auto *helper = static_cast<WlQtTextInputMethodHelper*>(base);, but d is a QWaylandQtTextInputMethodPrivate, not a WlQtTextInputMethodHelper, so this downcast is invalid and will be UB at runtime—please remove the WlQtTextInputMethodHelper inheritance/downcast and call send_enter/other methods directly via the actual type that implements the protocol.
  • The text input focus wiring relies heavily on private Qt headers (qwaylandtextinput_p.h, qwaylandqttextinputmethod_p.h, QObjectPrivate::get) and string-based metaObject()->className() checks; consider centralizing this logic behind a single helper and adding version/feature guards so that future Qt upgrades do not silently break text input handling.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `WlQtTextInputMethodHelper::setFocusCustom` implementation does `auto *base = static_cast<QtWaylandServer::qt_text_input_method_v1*>(d);` and later `auto *helper = static_cast<WlQtTextInputMethodHelper*>(base);`, but `d` is a `QWaylandQtTextInputMethodPrivate`, not a `WlQtTextInputMethodHelper`, so this downcast is invalid and will be UB at runtime—please remove the `WlQtTextInputMethodHelper` inheritance/downcast and call `send_enter`/other methods directly via the actual type that implements the protocol.
- The text input focus wiring relies heavily on private Qt headers (`qwaylandtextinput_p.h`, `qwaylandqttextinputmethod_p.h`, `QObjectPrivate::get`) and string-based `metaObject()->className()` checks; consider centralizing this logic behind a single helper and adding version/feature guards so that future Qt upgrades do not silently break text input handling.

## Individual Comments

### Comment 1
<location path="panels/dock/pluginmanagerextension.cpp" line_range="80-81" />
<code_context>
+            d->cursorRectangle = QRect();
+            d->preferredLanguage.clear();
+            d->hints = Qt::InputMethodHints();
+            auto *helper = static_cast<WlQtTextInputMethodHelper*>(base);
+            helper->send_enter(d->resource->handle, d->focusedSurface->resource());
+            
+            helper->send_input_direction_changed(d->resource->handle, int(qApp->inputMethod()->inputDirection()));
</code_context>
<issue_to_address>
**issue (bug_risk):** The static_cast to WlQtTextInputMethodHelper is undefined behavior because the underlying object is not of that type.

Here `base` is a `QWaylandQtTextInputMethodPrivate` (deriving from `QtWaylandServer::qt_text_input_method_v1`), not a `WlQtTextInputMethodHelper`. Downcasting a base pointer to an unrelated derived type is undefined behavior, even if they share a base. Since you only need `send_enter`, `send_input_direction_changed`, and `send_locale_changed`, call them directly on `base` (or move this logic into a non-inheriting helper) and remove the cast to `WlQtTextInputMethodHelper`.
</issue_to_address>

### Comment 2
<location path="panels/dock/pluginmanagerextension.cpp" line_range="808" />
<code_context>
         });
 }
+
+void PluginManager::setupTextInputProxy(QWaylandCompositor *compositor)
+{
+    // === IME 事件转发的完整链路说明 ===
</code_context>
<issue_to_address>
**issue (complexity):** Consider extracting the focus-handling, surfaceEnabled wiring, text-input state reset, and clipboard-sync logic into small helper functions to keep initialize() and the keyboardFocusChanged lambda easier to read and maintain.

You can keep all the behavior while making this code easier to follow by extracting some of the repeated / multi‑purpose logic into small helpers.

### 1. Split the `keyboardFocusChanged` lambda into protocol‑specific helpers

The lambda currently does: selection update, extension iteration, class name dispatch, private casts, and signal wiring. This can be made more readable by extracting the per‑protocol focus handling into small helpers:

```cpp
// In PluginManager (private methods):

void PluginManager::handleTextInputFocus(QObject *ext, QWaylandSurface *newFocus)
{
    auto *d = static_cast<QWaylandTextInputPrivate *>(QObjectPrivate::get(ext));
    d->setFocus(newFocus);
}

void PluginManager::handleTextInputV3Focus(QObject *ext, QWaylandSurface *newFocus)
{
    auto *d = static_cast<QWaylandTextInputV3Private *>(QObjectPrivate::get(ext));
    d->setFocus(newFocus);
}

void PluginManager::handleQtTextInputMethodFocus(QObject *ext, QWaylandSurface *newFocus)
{
    auto *qtMethodPriv = static_cast<QWaylandQtTextInputMethodPrivate *>(QObjectPrivate::get(ext));
    qtMethodPriv->inputPanelVisible = true;
    WlQtTextInputMethodHelper::setFocusCustom(qtMethodPriv, newFocus);
}
```

Then `setupTextInputProxy`’s lambda becomes easier to scan:

```cpp
QObject::connect(seat, &QWaylandSeat::keyboardFocusChanged, this,
    [this, seat](QWaylandSurface *newFocus, QWaylandSurface *oldFocus) {
        Q_UNUSED(oldFocus);

        if (newFocus)
            newFocus->updateSelection();

        const auto extensions = seat->extensions();
        for (auto *ext : extensions) {
            const QString className = ext->metaObject()->className();

            if (className == QStringLiteral("QWaylandTextInput")) {
                handleTextInputFocus(ext, newFocus);
            } else if (className == QStringLiteral("QWaylandTextInputV3")) {
                handleTextInputV3Focus(ext, newFocus);
            } else if (className == QStringLiteral("QWaylandQtTextInputMethod")) {
                handleQtTextInputMethodFocus(ext, newFocus);
            }

            connectSurfaceEnabledOnce(ext);
        }
    });
```

### 2. Deduplicate `surfaceEnabled` connections

You repeat the same `QObject::connect` three times. A small helper keeps the pattern in one place and documents intent:

```cpp
void PluginManager::connectSurfaceEnabledOnce(QObject *ext)
{
    QObject::connect(ext, SIGNAL(surfaceEnabled(QWaylandSurface*)),
                     this, SLOT(onTextInputSurfaceEnabled(QWaylandSurface*)),
                     Qt::UniqueConnection);
}
```

Used in the loop above, this removes duplication and makes it clear that every text input extension should share the same `surfaceEnabled` behavior.

### 3. Reduce incidental complexity in `WlQtTextInputMethodHelper::setFocusCustom`

`setFocusCustom` mixes: resource lookup, leave/enter protocol messages, state reset, and focus listener handling. You can factor out the “state reset” into a tiny helper to make the control flow clearer:

```cpp
static void resetQtTextInputState(QWaylandQtTextInputMethodPrivate *d)
{
    d->surroundingText.clear();
    d->cursorPosition = 0;
    d->anchorPosition = 0;
    d->absolutePosition = 0;
    d->cursorRectangle = QRect();
    d->preferredLanguage.clear();
    d->hints = Qt::InputMethodHints();
}
```

Then:

```cpp
static void setFocusCustom(QWaylandQtTextInputMethodPrivate *d, QWaylandSurface *surface)
{
    auto *base = static_cast<QtWaylandServer::qt_text_input_method_v1*>(d);
    if (d->focusedSurface == surface)
        return;

    QtWaylandServer::qt_text_input_method_v1::Resource *newResource = nullptr;
    if (surface && surface->client()) {
        auto resources = base->resourceMap().values(surface->client()->client());
        if (!resources.isEmpty())
            newResource = resources.first();
    }

    if (d->focusedSurface) {
        if (d->resource)
            base->send_leave(d->resource->handle, d->focusedSurface->resource());
        d->focusDestroyListener.reset();
    }

    d->focusedSurface = surface;
    d->resource = newResource;

    if (d->resource && d->focusedSurface) {
        resetQtTextInputState(d);

        auto *helper = static_cast<WlQtTextInputMethodHelper*>(base);
        helper->send_enter(d->resource->handle, d->focusedSurface->resource());
        helper->send_input_direction_changed(d->resource->handle,
                                             int(qApp->inputMethod()->inputDirection()));
        helper->send_locale_changed(d->resource->handle,
                                    qApp->inputMethod()->locale().bcp47Name());

        d->focusDestroyListener.listenForDestruction(surface->resource());
        if (d->inputPanelVisible && d->enabledSurfaces.values().contains(surface))
            qApp->inputMethod()->show();
    }
}
```

This keeps the inheritance requirement but makes the method read as higher‑level steps.

### 4. Isolate clipboard setup out of `initialize()`

`initialize()` is becoming an orchestration method; the clipboard logic can be extracted without behavior change:

```cpp
void PluginManager::setupClipboardSync(QWaylandCompositor *compositor)
{
    compositor->setRetainedSelectionEnabled(true);

    if (auto *clipboard = QGuiApplication::clipboard()) {
        QObject::connect(clipboard, &QClipboard::changed, this,
                         [compositor](QClipboard::Mode mode) {
            if (mode == QClipboard::Clipboard || mode == QClipboard::Selection) {
                if (const QMimeData *mimeData = QGuiApplication::clipboard()->mimeData(mode))
                    compositor->overrideSelection(mimeData);
            }
        });

        if (const QMimeData *mimeData = clipboard->mimeData(QClipboard::Clipboard))
            compositor->overrideSelection(mimeData);
    }
}
```

And in `initialize()`:

```cpp
init(compositor->display(), 1);

// IME manager
auto qtTextInputMethodManager = new QWaylandQtTextInputMethodManager(compositor);
qtTextInputMethodManager->initialize();

// Mouse focus + IME proxy + clipboard
setupMouseFocusListener();
setupTextInputProxy(compositor);
setupClipboardSync(compositor);
```

This keeps `initialize()` at a “wire things together” level, with the detailed logic in dedicated helpers.
</issue_to_address>

### Comment 3
<location path="panels/dock/pluginmanagerextension_p.h" line_range="84" />
<code_context>
     //处理鼠标焦点给到相应插件
     void setupMouseFocusListener();
+    //处理 IME 输入代理:将插件进程的 text input 请求透传给外层 compositor
+    void setupTextInputProxy(QWaylandCompositor *compositor);

 Q_SIGNALS:
</code_context>
<issue_to_address>
**issue (complexity):** Consider encapsulating the IME/text-input helper implementation in the cpp file and visually grouping the related methods in the header to keep PluginManager’s interface thinner and more focused.

You can keep the new behavior while reducing header-level complexity by pushing the protocol-specific IME helper details into the cpp and lightly grouping the new methods.

### 1. Hide `WlQtTextInputMethodHelper` from the header

If `WlQtTextInputMethodHelper` doesn’t need to be referenced from other translation units, move its definition into an anonymous namespace in the cpp and keep only a minimal handle in the header (ideally in a private impl struct).

**Header (keep thin):**
```cpp
// pluginmanager_p.h

class PluginManagerPrivate
{
public:
    // ...

    // opaque pointer to IME helper, defined in cpp
    std::unique_ptr<void> textInputHelper;
};
```

**Cpp (keep details local and protocol-specific):**
```cpp
// pluginmanager.cpp
namespace {

// fully define the helper here, using private Qt/Wayland API as needed
struct WlQtTextInputMethodHelper
{
    void setupTextInputProxy(QWaylandCompositor *compositor);
    void onTextInputSurfaceEnabled(QWaylandSurface *surface);
    // ...
};

} // anonymous namespace

PluginManagerPrivate::PluginManagerPrivate()
    : textInputHelper(std::make_unique<WlQtTextInputMethodHelper>())
{
}
```

Then in methods:

```cpp
void PluginManager::setupTextInputProxy(QWaylandCompositor *compositor)
{
    auto *helper = static_cast<WlQtTextInputMethodHelper*>(d->textInputHelper.get());
    helper->setupTextInputProxy(compositor);
}

void PluginManager::onTextInputSurfaceEnabled(QWaylandSurface *surface)
{
    auto *helper = static_cast<WlQtTextInputMethodHelper*>(d->textInputHelper.get());
    helper->onTextInputSurfaceEnabled(surface);
}
```

This keeps the private header free from protocol-heavy types and reduces mental load for anyone reading `pluginmanager_p.h`.

### 2. Group the IME-related methods in the header

Make the IME responsibility visually distinct from the rest of `PluginManager` to avoid the “god object” feel and clarify intent:

```cpp
class PluginManager : public QWaylandCompositorExtensionTemplate<PluginManager>
{
    Q_OBJECT
public:
    // ...

    void setEmbedPanelMinHeight(int height);
    QSize dockSize() const;
    void setDockSize(const QSize &newDockSize);
    void removePluginSurface(PluginSurface *plugin);

    // Mouse focus management
    void setupMouseFocusListener();

    // IME / text input proxying: forward plugin text input to outer compositor
    void setupTextInputProxy(QWaylandCompositor *compositor);

Q_SIGNALS:
    // ...

private Q_SLOTS:
    // Theme / appearance
    void onFontChanged();
    void onActiveColorChanged();
    void onThemeChanged();

    // IME / text input
    void onTextInputSurfaceEnabled(QWaylandSurface *surface);
```

This keeps responsibilities discoverable and makes future IME-related additions less likely to blur with unrelated behavior, without changing any functionality you’ve introduced.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@yixinshark yixinshark force-pushed the fix-chineseInput branch 5 times, most recently from dec49f5 to b36c7aa Compare March 5, 2026 09:23
@yixinshark yixinshark force-pushed the fix-chineseInput branch 4 times, most recently from 0d3c169 to e01b6ff Compare March 5, 2026 11:32
This commit enables Chinese input methods (like fcitx) to correctly interact with
Wayland dock plugin windows.

The fix introduces an explicit text input proxy routing:
1. Since QWaylandSeat::setKeyboardFocus() does not update text input
   protocols automatically, we manually assign the focused surface to
   all text input global extensions (QWaylandTextInput/V3 &
   QWaylandQtTextInputMethod) via private APIs.
2. In QWaylandQtTextInputMethod, the d->resource was missing during continuous
   window focus change, breaking client messages like update_cursor_rectangle.
   We intercept focus management using a custom WlQtTextInputMethodHelper.
3. Connecting surfaceEnabled signal to automatically invoke forceActiveFocus()
   on the QWaylandQuickItem rendering the surface frame. This is crucial
   because Qt requires active focus to correctly route QInputMethodEvent downwards
   to the client.

Log: support wayland text input on plugin popup windows
Pms: BUG-323547
To fix pasting text in Wayland plugin windows, this commit ensures that nested
Wayland clients can correctly receive host clipboard contents.

Wayland compositor (dde-shell) does not automatically synchronize the host system's
clipboard to its nested clients. This caused plugin popup windows to be unable to
paste text that was copied from outside applications.

This fix enables retained selection in QWaylandCompositor and explicitly listens to
QClipboard::changed from the host environment to forward the QMimeData to the
compositor via overrideSelection().

Log: fix pasting text in Wayland plugin windows
Pms: BUG-329435
@deepin-ci-robot
Copy link

deepin pr auto review

这段代码主要实现了在 Wayland Compositor 中处理输入法(IME)和剪贴板的代理逻辑,特别是针对 Qt TextInput 协议的焦点管理和事件转发。以下是对代码的详细审查和改进建议:

1. 语法与逻辑审查

1.1 头文件包含

  • 问题:使用了私有头文件 #include <private/qwaylandtextinput_p.h> 等。
  • 风险:使用 Qt 私有 API (_p.h) 是不稳定的,不同版本的 Qt 可能会更改或移除这些头文件,导致编译失败或运行时崩溃。
  • 建议
    • 尽量通过官方公共 API 实现功能。
    • 如果必须使用私有 API,添加 #if QT_VERSION 宏来适配不同 Qt 版本,并在代码注释中明确说明依赖关系和风险。
    • 考虑向 Qt 官方提交 Feature Request,将必要的接口公开。

1.2 焦点管理逻辑

  • 问题:在 setupTextInputProxy 中,通过 className 字符串比较来识别扩展类型。
    if (className == QStringLiteral("QWaylandTextInput")) { ... }
  • 风险
    • 字符串比较效率较低。
    • 依赖于 Qt 内部类名,如果 Qt 更改类名(例如添加命名空间),代码将失效。
  • 建议
    • 使用 qobject_cast 替代字符串比较,但这需要类有 Q_OBJECT 宏且类型信息可用。
    • 如果无法转换,保留字符串比较但添加断言检查,并在日志中记录警告。

1.3 信号连接

  • 问题:在 setupTextInputProxy 中使用 Qt::UniqueConnection 连接信号:
    QObject::connect(ext, SIGNAL(surfaceEnabled(QWaylandSurface*)),
        this, SLOT(onTextInputSurfaceEnabled(QWaylandSurface*)),
        Qt::UniqueConnection);
  • 风险
    • SIGNALSLOT 宏是旧式语法,编译时不检查类型安全。
    • Qt::UniqueConnection 在连接 lambda 时无效(但这里连接的是槽函数,所以是有效的)。
  • 建议
    • 使用函数指针语法或 lambda 表达式以提高类型安全:
      QObject::connect(ext, &QWaylandTextInput::surfaceEnabled,
          this, &PluginManager::onTextInputSurfaceEnabled,
          Qt::UniqueConnection);
    • 如果信号是私有的,确保连接逻辑在类内部或友元类中。

2. 代码质量

2.1 注释

  • 优点:代码中有详细的注释说明 IME 事件转发链路,非常清晰。
  • 建议
    • 将注释中的链路说明提取到文档或单独的设计文档中,便于维护。
    • WlQtTextInputMethodHelper::setFocusCustom 等关键函数中添加简短的参数和返回值说明。

2.2 魔法值

  • 问题:代码中直接使用字符串字面量(如 "QWaylandTextInput")。
  • 建议:定义为常量或使用枚举:
    namespace WaylandClasses {
        constexpr auto TextInput = "QWaylandTextInput";
        constexpr auto TextInputV3 = "QWaylandTextInputV3";
        // ...
    }

2.3 错误处理

  • 问题:在 onTextInputSurfaceEnabled 中,surface->views() 可能为空,但未检查。
  • 建议
    const auto views = surface->views();
    if (views.isEmpty()) {
        qWarning() << "No views found for surface:" << surface;
        return;
    }

3. 性能优化

3.1 遍历扩展

  • 问题:每次键盘焦点变化时遍历所有扩展。
  • 建议
    • 缓存已找到的扩展对象,避免重复遍历。
    • 使用 QHash<QByteArray, QObject*> 存储扩展映射,键为类名。

3.2 信号连接

  • 问题:在 keyboardFocusChanged 中重复连接信号。
  • 建议
    • 使用 Qt::UniqueConnection 已经避免了重复连接,但可以进一步优化:
      • 在初始化时连接一次,而不是每次焦点变化时检查。
      • 使用 QPointerQWeakPointer 跟踪扩展对象的生命周期。

4. 安全性

4.1 私有 API 访问

  • 问题:通过 QObjectPrivate::get 访问私有成员。
  • 风险
    • 破坏了封装性,可能导致未定义行为。
    • 如果 Qt 更改私有类的布局,代码会崩溃。
  • 建议
    • 限制私有 API 的使用范围,封装在单独的辅助类中。
    • 添加运行时检查,确保对象类型正确:
      auto *d = dynamic_cast<QWaylandTextInputPrivate*>(QObjectPrivate::get(ext));
      if (!d) {
          qWarning() << "Failed to cast to QWaylandTextInputPrivate";
          continue;
      }

4.2 剪贴板同步

  • 问题:在剪贴板变化时直接调用 compositor->overrideSelection(mimeData)
  • 风险
    • 如果 mimeData 很大(如文件列表),可能导致性能问题。
    • 未检查 mimeData 是否为空。
  • 建议
    if (mimeData && !mimeData->formats().isEmpty()) {
        compositor->overrideSelection(mimeData);
    }

5. 其他建议

5.1 内存管理

  • 问题qtTextInputMethodManager 使用 new 创建,但未显示释放。
  • 建议
    • 使用 QScopedPointerstd::unique_ptr 管理对象生命周期。
    • 确保在 PluginManager 析构时释放资源。

5.2 测试覆盖

  • 建议
    • 添加单元测试覆盖焦点切换、输入法启用/禁用、剪贴板同步等场景。
    • 测试不同 Qt 版本(5.15, 6.x)的兼容性。

改进后的代码示例

void PluginManager::setupTextInputProxy(QWaylandCompositor *compositor)
{
    QWaylandSeat *seat = compositor->defaultSeat();
    if (!seat)
        return;

    // 缓存扩展对象
    static QHash<QByteArray, QObject*> extensionCache;
    if (extensionCache.isEmpty()) {
        const auto extensions = seat->extensions();
        for (auto *ext : extensions) {
            extensionCache.insert(ext->metaObject()->className(), ext);
        }
    }

    QObject::connect(seat, &QWaylandSeat::keyboardFocusChanged, this, [this, seat]
        (QWaylandSurface *newFocus, QWaylandSurface *oldFocus) {
        Q_UNUSED(oldFocus);

        if (newFocus) {
            newFocus->updateSelection();
        }

        // 使用缓存的扩展对象
        for (auto it = extensionCache.constBegin(); it != extensionCache.constEnd(); ++it) {
            const QByteArray &className = it.key();
            QObject *ext = it.value();

            if (className == "QWaylandTextInput") {
                auto *d = dynamic_cast<QWaylandTextInputPrivate*>(QObjectPrivate::get(ext));
                if (d) {
                    d->setFocus(newFocus);
                    QObject::connect(ext, &QWaylandTextInput::surfaceEnabled,
                        this, &PluginManager::onTextInputSurfaceEnabled,
                        Qt::UniqueConnection);
                }
            } else if (className == "QWaylandTextInputV3") {
                // 类似处理...
            } else if (className == "QWaylandQtTextInputMethod") {
                // 类似处理...
            }
        }
    });
}

总结

这段代码功能完整,但存在以下主要问题:

  1. 过度依赖 Qt 私有 API,维护成本高。
  2. 字符串比较和动态遍历影响性能。
  3. 缺少错误处理和边界检查。
  4. 内存管理不明确。

建议逐步重构,减少对私有 API 的依赖,优化性能,并增强健壮性。

@deepin-ci-robot
Copy link

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: tsic404, yixinshark

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@yixinshark
Copy link
Contributor Author

/forcemerge

@deepin-bot
Copy link

deepin-bot bot commented Mar 5, 2026

This pr force merged! (status: blocked)

@deepin-bot deepin-bot bot merged commit 6832d02 into linuxdeepin:master Mar 5, 2026
9 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants